﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FacensaNet.BudgetDotCom.DomainModel;
using FacensaNet.BudgetDotCom.DataAbstractionLayer.Core;

namespace FacensaNet.BudgetDotCom.DataAbstractionLayer.Linq
{
    internal abstract class DataAccessObject<T> : IDataAccessObject<T> where T : class, IEntidade, new()
    {
        #region Campos e propriedades

        private IBudgetContainer container;

        private readonly object syncRoot = new object();

        /// <summary>
        /// Cria uma instância do container ou carrega a que já existe.
        /// </summary>
        /// <returns>
        /// Uma instância de um <see>BudgetContainer</see>.
        /// </returns>
        protected IBudgetContainer CriaContainer()
        {
            // tratemento de concorrência, para garantir que mesmo que duas threads 
            // executem esse código ao mesmo tempo apenas uma instância será criada
            if (container == null)
            {
                lock (syncRoot)
                {
                    if (container == null)
                    {
                        container = new BudgetContainer();
                    }
                }
            }

            return container;
        }

        #endregion

        /// <summary>
        /// Cria uma nova instância da entidade manipulada por esse DAO.
        /// </summary>
        /// <returns>
        /// Uma nova instância da entidade que esse DAO manipula.
        /// </returns>
        /// <remarks>
        /// Entidades deveriam sempre ser criadas através deste método de
        /// fábrica, já que a DAL pode precisar executar algum código de
        /// inicialização para elas.
        /// </remarks>
        public virtual T CriaEntidade()
        {
            return new T();
        }

        /// <summary>
        /// Buscas todos os elementos da tabela.
        /// </summary>
        /// <returns>
        /// Uma lista contendo todos os elementos da tabela.
        /// </returns>
        public virtual IEnumerable<T> BuscaTodos()
        {
            return CriaContainer().PegaTodos<T>();
        }

        /// <summary>
        /// Busca elementos que contenham uma determinada <c>string</c> em seu nome.
        /// </summary>
        /// <param name="nome">O nome a ser usado. A busca retornará entidades que contenham essa estring.</param>
        /// <returns>
        /// Uma lista de entidades que batem com o critério de busca.
        /// </returns>
        public virtual IEnumerable<T> BuscaPorNome(string nome)
        {
            return from o in CriaContainer().PegaTodos<T>()
                   where o.Nome.Contains(nome)
                   select o;
        }

        /// <summary>
        /// Busca de entidade por id.
        /// </summary>
        /// <param name="id">O id a ser procurado.</param>
        /// <returns>
        /// Uma instância de uma entidade com o id passado, caso seja encontrada.
        /// Caso não seja encontrada, retornará <c>null</c>.
        /// </returns>
        public virtual T BuscaPorId(int id)
        {
            var q = from o in CriaContainer().PegaTodos<T>()
                   where o.Id.Equals(id)
                   select o;

            return q.SingleOrDefault();
        }

        protected IEnumerable<KeyValuePair<int, string>> ConverteEmLookup(IEnumerable<T> lista)
        {
            var q = from o in lista
                    select new KeyValuePair<int, string>(o.Id, o.Nome);

            return q;
        }

        public virtual IEnumerable<KeyValuePair<int, string>> BuscaLookup()
        {
            return ConverteEmLookup(CriaContainer().PegaTodos<T>());
        }

        #region IDisposable Members

        public void Dispose()
        {
            if (container != null)
            {
                container.Dispose();
            }
        }

        #endregion

        #region Métodos protegidos/privados

        protected void RealizaRemocao(T entidade)
        {
            CriaContainer().RemoveEntidade<T>(entidade);
        }

        protected void RealizaSalvamento(T entidade)
        {
            if (Entidade.EhNova(entidade))
            {
                CriaContainer().InsereEntidade<T>(entidade);
            }
            else
            {
                CriaContainer().AtualizaEntidade<T>(entidade);
            }
        }

        #endregion
    }
}
